package fons.model.ontology;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

public class ValueSpace {
	private final Set<Dimension> dimensions;	
	private final Integer hash;
	private Collection<Value> valuesHelper;
	private List<Value> values = null;
	
	private final Map<Value, Double> normalized = Maps.newHashMap();
	private double minNormalized = 0.0;
	private double maxNormalized = 1.0;
	
	public ValueSpace (Collection<? extends Dimension> space) {
		this.dimensions = Sets.newHashSet();
		this.dimensions.addAll(space);
		this.hash = generateHash();

		Collection<Value> values = Value.getSmallestValueSet(dimensions);
		this.valuesHelper = values;
	}
	
	public Collection<Value> getValues() {
		if (values == null) {
			values = collectValues();
		} 

		return values;
	}
	
	public Set<Dimension> getDimensions () {
		return Collections.unmodifiableSet(dimensions);
	}

	private List<Value> collectValues() {
		List<Value> result = Lists.newArrayList(Filters.allOf(dimensions).filter(valuesHelper));
		valuesHelper = null;
		return result;
	}

	public Collection<Value> filter (Filter filter) {
		return filter.filter(getValues());
	}
	
	public ValueSpace subSpace (Dimension dimension) {
		return this.subSpace(Sets.newHashSet(dimension));
	}
	
	public ValueSpace subSpace (ValueSpace other) {
		return this.subSpace(other.dimensions);
	}

	public ValueSpace subSpace (Dimension... dimensions) {
		return this.subSpace(Sets.newHashSet(dimensions));
	}	

	public ValueSpace subSpace (Collection<? extends Dimension> partition) {
		Set<Dimension> tmp = Sets.newHashSet(dimensions);
		tmp.addAll(partition);
		ValueSpace sub = new ValueSpace(tmp);
		for (Value value: sub.getValues()) {
			sub.normalized.put(value, this.normalized.get(value));
		}
		return sub;
	}
	
	private int generateHash() {
		final int prime = 31;
		int result = 1;
		result = prime * result
				+ ((dimensions == null) ? 0 : dimensions.hashCode());
		return result;
	}
	
	@Override
	public int hashCode() {
		return hash;
	}
	
	public void normalize (double min, double max) {
		normalize(min, max, true);
	}
	
	public void normalize (double min, double max, boolean absolute) {
		this.minNormalized = min;
		this.maxNormalized = max;
		normalize(absolute);
	}
	
	public void normalize () {
		normalize(true);
	}
	
	public void normalize (boolean absolute) {
		Collection<Value> values = getValues();
		
		double min = Double.NaN, max = Double.NaN;
		for (Value value: values) {
			if (Double.isNaN(min)) min = value.getValue();
			else if (value.getValue() < min) min = value.getValue();
			
			if (Double.isNaN(max)) max = value.getValue();
			else if (value.getValue() > max) max = value.getValue();
		}
		
		if (absolute && min > 0) min = 0;
		
		for (Value value: getValues()) {
			double norm =  minNormalized + (value.getValue() / max) * (maxNormalized - minNormalized);
			normalized.put(value, norm);
		}
	}
	
	public double getNormalized(Value value) {
		if (value == Context.missingValue) return minNormalized;
		if (normalized.isEmpty()) normalize();
		return normalized.get(value);
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		ValueSpace other = (ValueSpace) obj;
		if (dimensions == null) {
			if (other.dimensions != null)
				return false;
		} else if (!dimensions.equals(other.dimensions))
			return false;
		return true;
	}
	
	public static ValueSpace of (Match match) {
		return new ValueSpace(match.getDimensions());
	}
	
	public static ValueSpace of (Collection<? extends Dimension> dims) {
		return new ValueSpace(dims);
	}
	
	public static ValueSpace of (Dimension... dimensions) {
		return new ValueSpace(Sets.newHashSet(dimensions));
	}

	private boolean sorted = false;
	public Value getMin(Set<Dimension> filter) {
		List<Value> values = (List<Value>) getValues();
		if (values.size() == 0) return Context.missingValue;
		if (!sorted) {
			Collections.sort(values, Value.COMPARE_VALUE_ONLY);
			sorted = true;
		}
		
		for (int i = 0; i < values.size(); ++i) {
			if (values.get(i).hasAnyOf(filter) != null)
				return values.get(i);
		}
		return Context.missingValue;
	}
	
	public Iterable<Value> getSortedValues () {
		if (!sorted) {
			Collections.sort(values, Value.COMPARE_VALUE_ONLY);
			sorted = true;
		}
		return Collections.unmodifiableList(values);
	}
	
	public Value getMax(Set<Dimension> filter) {
		List<Value> values = (List<Value>) getValues();
		if (values.size() == 0) return Context.missingValue;
		
		if (!sorted) {
			Collections.sort(values, Value.COMPARE_VALUE_ONLY);
			sorted = true;
		}
		
		for (int i = values.size()-1; i > 0; --i) {
			if (values.get(i).hasAnyOf(filter) != null)
				return values.get(i);
		}
		return Context.missingValue;
	}
	
	public Map<Dimension, ValueSpace> split (Set<Dimension> splittingDimensions) {
		Map<Dimension, ValueSpace> splits = Maps.newHashMap();
		for (Dimension splitter: splittingDimensions) {
			Set<Dimension> dimensions = Sets.newHashSet(this.dimensions);
			dimensions.add(splitter);
			final ValueSpace splitSpace = new ValueSpace(dimensions);
			splitSpace.values = Lists.newArrayList();
			splits.put(splitter, splitSpace);
		}
		
		for (Value value: getValues()) {
			for (Dimension splitter: splittingDimensions) {
				final ValueSpace splitSpace = splits.get(splitter);
				if (value.is(splitter)) splitSpace.values.add(value);
			}
		}
		
		return splits;
	}
	
}
